package me.zhengjie.modules.system.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.ecc.shuffle.upgrade.RuleFirer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import me.zhengjie.config.MappingProperties;
import me.zhengjie.config.ShuffleProperties;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.system.domain.Requirement;
import me.zhengjie.modules.system.domain.SafeRiskCase;
import me.zhengjie.modules.system.domain.SafeRiskItem;
import me.zhengjie.modules.system.domain.SafeRiskType;
import me.zhengjie.modules.system.domain.System;
import me.zhengjie.modules.system.repository.RequirementRepository;
import me.zhengjie.modules.system.repository.SafeRiskCaseRepository;
import me.zhengjie.modules.system.repository.SafeRiskItemRepository;
import me.zhengjie.modules.system.repository.SafeRiskTypeRepository;
import me.zhengjie.modules.system.service.SafeTestTypeService;
import me.zhengjie.modules.system.service.dto.RecommendParam;
import me.zhengjie.modules.system.service.dto.SafeRiskItemQueryCriteria;
import me.zhengjie.modules.system.service.dto.SafeRiskTypeDto;
import me.zhengjie.modules.system.service.dto.SafeRiskTypeQueryCriteria;
import me.zhengjie.modules.system.service.mapstruct.SafeRiskItemMapper;
import me.zhengjie.modules.system.service.mapstruct.SafeRiskTypeMapper;
import me.zhengjie.util.ExcelListener;
import me.zhengjie.utils.PageUtil;
import me.zhengjie.utils.QueryHelp;
import me.zhengjie.utils.StringUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * SafeRiskTypeServiceImpl
 *
 * @author guoyuan
 * @version 1.0.0
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "safeTestType")
public class SafeTestTypeServiceImpl implements SafeTestTypeService {

    private static final String POINT = ".";
    private final SafeRiskTypeRepository safeRiskTypeRepository;
    private final SafeRiskItemRepository safeRiskItemRepository;
    private final SafeRiskCaseRepository safeRiskCaseRepository;
    private final RequirementRepository requirementRepository;
    private final SafeRiskItemMapper safeRiskItemMapper;
    private final SafeRiskTypeMapper safeRiskTypeMapper;
    private final MappingProperties mappingProperties;
    private final ShuffleProperties shuffleProperties;

    @Override
    public List<SafeRiskType> query() {
        return safeRiskTypeRepository.findAll();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(List<SafeRiskTypeDto> resources, String parentId) {
        SafeRiskType parent = null;
        if (StringUtils.isNotBlank(parentId)) {
            final Optional<SafeRiskType> byId = safeRiskTypeRepository.findById(parentId);
            if (byId.isPresent()) {
                parent = byId.get();
            }
        }
        final SafeRiskType finalParent = parent;
        for (int i = 0; i < resources.size(); i++) {
            final SafeRiskTypeDto dto = resources.get(i);
            if (dto == null) {
                continue;
            }
            dto.setLevel(String.valueOf(i + 1));
            saveDto(finalParent, dto);
        }
    }

    public void saveDto(SafeRiskType parent, SafeRiskTypeDto typeDto) {
        if (typeDto == null) {
            return;
        }
        final String name = typeDto.getName();
        final SafeRiskType safeRiskType = safeRiskTypeMapper.toEntity(typeDto);
        final String parentId = parent == null ? null : parent.getId();
        final String path = parent == null ? name : parent.getPath() + POINT + name;
        safeRiskType.setParentId(parentId);
        safeRiskType.setPath(path);
        final SafeRiskType save = safeRiskTypeRepository.save(safeRiskType);
        final List<SafeRiskTypeDto> children = typeDto.getChildren();
        if (children == null || children.isEmpty()) {
            return;
        }
        for (int i = 0; i < children.size(); i++) {
            SafeRiskTypeDto child = children.get(i);
            if (child == null) {
                continue;
            }
            child.setLevel(String.valueOf(i + 1));
            saveDto(save, child);
        }
    }

    @Override
    public List<SafeRiskTypeDto> queryAll(SafeRiskTypeQueryCriteria criteria) throws Exception {
        final Map<String, List<SafeRiskType>> childrenMap = getChildrenTypeMap();
        updateCriteria(criteria);
        final List<SafeRiskType> allParent = safeRiskTypeRepository.findAll((root, query, cb) -> QueryHelp.getPredicate(root, criteria, cb));
        final List<SafeRiskTypeDto> safeRiskTypeDtos = safeRiskTypeMapper.toDto(allParent);
        for (SafeRiskTypeDto dto : safeRiskTypeDtos) {
            if (childrenMap == null || childrenMap.isEmpty()) {
                break;
            }
            setChildren(dto, childrenMap);
        }
        return safeRiskTypeDtos;
    }

    private Map<String, List<SafeRiskType>> getChildrenTypeMap() {
        final List<SafeRiskType> all = safeRiskTypeRepository.findAll();
        if (all.isEmpty()) {
            return Maps.newHashMap();
        }
        return all.parallelStream()
                .filter(type -> !StringUtils.isEmpty(type.getParentId()))
                .collect(Collectors.groupingBy(SafeRiskType::getParentId, Collectors.toList()));
    }

    private void setChildren(SafeRiskTypeDto parent, Map<String, List<SafeRiskType>> childrenMap) {
        if (parent == null) {
            return;
        }
        final String parentId = parent.getId();
        final List<SafeRiskType> childrenList = childrenMap.get(parentId);
        if (childrenList == null || childrenList.isEmpty()) {
            return;
        }
        final List<SafeRiskTypeDto> childrenDtoList = safeRiskTypeMapper.toDto(childrenList);
        parent.setChildren(childrenDtoList);
        for (SafeRiskTypeDto dto : childrenDtoList) {
            setChildren(dto, childrenMap);
        }
    }

    private void updateCriteria(SafeRiskTypeQueryCriteria criteria) throws IllegalAccessException {
        if (criteria == null) {
            criteria = new SafeRiskTypeQueryCriteria();
        }
        criteria.setParentIdIsNull(true);
        List<Field> fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>());
        for (Field field : fields) {
            //设置对象的访问权限，保证对private的属性的访问
            field.setAccessible(true);
            Object val = field.get(criteria);
            if (!"parentId".equals(field.getName())) {
                continue;
            }
            if (ObjectUtil.isNotNull(val)) {
                criteria.setParentIdIsNull(null);
                break;
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void upload(MultipartFile file) throws Exception {
        final MappingProperties.Sheets sheets = mappingProperties.getSheets();
        saveExcel2Item(file, sheets.getItemSheets());
        saveExcel2Case(file, sheets.getCaseSheets());
    }

    private void saveExcel2Item(MultipartFile file, String... sheetNames) throws Exception {
        if (sheetNames == null || sheetNames.length == 0) {
            return;
        }
        for (int i = 0; i < sheetNames.length; i++) {
            String sheetName = sheetNames[i];
            List<SafeRiskItem> readList = EasyExcel.read(file.getInputStream(), new ExcelListener())
                    .head(SafeRiskItem.class)
                    .sheet(sheetName)
                    .doReadSync();
            readList = readList.stream()
                    .filter(s -> !StringUtils.isEmpty(s.getEnvType()))
                    .collect(Collectors.toList());
            final String sceneType = String.valueOf(i);
            boolean businessTypeIsNull = false;
            for (SafeRiskItem item : readList) {
                if (StringUtils.isBlank(item.getBusinessType())) {
                    // 功能场景风险映射 sheet 中，业务类别为空
                    businessTypeIsNull = true;
                    break;
                } else if (StringUtils.isBlank(item.getEnvType())) {
                    throw new Exception("场景类别不能为空");
                } else if (StringUtils.isBlank(item.getAppEnv())) {
                    throw new Exception("应用场景不能为空");
                } else if (StringUtils.isBlank(item.getRisk())) {
                    throw new Exception("风险项不能为空");
                }
            }
            int functionIndex = 0;
            if (businessTypeIsNull) {
                functionIndex = 1;
            }
            readList.forEach(item -> item.setSceneType(sceneType));
            saveExcel2TypeAndItem(readList, sceneType, null, functionIndex, SafeRiskItem::getBusinessType,
                    SafeRiskItem::getEnvType, SafeRiskItem::getAppEnv, SafeRiskItem::getRisk);
        }
    }

    @SafeVarargs
    private final void saveExcel2TypeAndItem(List<SafeRiskItem> readList, String sceneType, SafeRiskType parent,
                                             int functionIndex, Function<SafeRiskItem, String>... functions) {
        if (functions == null || functions.length == 0) {
            return;
        }
        if (functionIndex < functions.length) {
            AtomicInteger level = new AtomicInteger(0);
            readList.parallelStream()
                    .collect(Collectors.groupingBy(functions[functionIndex], Collectors.toList()))
                    .forEach((name, list) -> {
                        final int andGet = level.addAndGet(1);
                        final int nextFunctionIndex = functionIndex + 1;
                        final String parentId = parent == null ? null : parent.getId();
                        final String path = parent == null ? name : parent.getPath() + POINT + name;
                        // 如果name为空，则上一步collect会抛出空指针，不会进入forEach
                        SafeRiskType save = safeRiskTypeRepository.save(new SafeRiskType(sceneType, path, String.valueOf(andGet), name, parentId));
                        saveExcel2TypeAndItem(list, sceneType, save, nextFunctionIndex, functions);
                    });
        } else {
            readList.forEach(item -> item.setSafeRiskType(parent));
            safeRiskItemRepository.saveAll(readList);
        }
    }

    private void saveExcel2Case(MultipartFile file, String... sheetNames) throws IOException {
        if (sheetNames == null || sheetNames.length == 0) {
            return;
        }
        for (int i = 0; i < sheetNames.length; i++) {
            String sheetName = sheetNames[i];
            List<SafeRiskCase> readList = EasyExcel.read(file.getInputStream(), new ExcelListener())
                    .head(SafeRiskCase.class)
                    .sheet(sheetName)
                    .doReadSync();
            readList = readList.stream()
                    .filter(s -> !StringUtils.isEmpty(s.getCaseId()))
                    .collect(Collectors.toList());
            final String sceneType = String.valueOf(i);
            readList.forEach(aCase -> aCase.setSceneType(sceneType));
            safeRiskCaseRepository.saveAll(readList);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        final Optional<SafeRiskType> byId = safeRiskTypeRepository.findById(id);
        if (!byId.isPresent()) {
            throw new BadRequestException("场景风险类别不存在");
        }
        deleteChildreAndItem(byId.get());
    }

    private void deleteChildreAndItem(SafeRiskType type) {
        if (type == null) {
            return;
        }
        final List<SafeRiskType> childreList = safeRiskTypeRepository.findAllByParentId(type.getId());
        if (childreList == null || childreList.isEmpty()) {
            safeRiskItemRepository.deleteAllBySafeRiskType(type);
        } else {
            childreList.forEach(this::deleteChildreAndItem);
        }
        safeRiskTypeRepository.delete(type);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(SafeRiskTypeDto typeDto, String parentId) {
        if (typeDto == null) {
            return;
        }
        final String id = typeDto.getId();
        if (StringUtils.isBlank(id)) {
            throw new BadRequestException("场景风险类别主键为空");
        }
        final String name = typeDto.getName();
        if (!safeRiskTypeRepository.existsById(id)) {
            throw new BadRequestException("场景风险类别[" + name + "]不存在");
        }
        final Optional<SafeRiskType> byId = safeRiskTypeRepository.findById(parentId);
        if (!byId.isPresent()) {
            throw new BadRequestException("场景风险类别[" + name + "]父级不存在");
        }
        editDto(byId.get(), typeDto);
    }

    private void editDto(SafeRiskType parent, SafeRiskTypeDto typeDto) {
        if (parent == null || typeDto == null) {
            return;
        }
        final String name = typeDto.getName();
        final String parentId = parent.getId();
        final SafeRiskType safeRiskType = safeRiskTypeMapper.toEntity(typeDto);
        safeRiskType.setPath(parent.getPath() + POINT + name);
        safeRiskType.setParentId(parentId);
        final SafeRiskType save = safeRiskTypeRepository.save(safeRiskType);
        final List<SafeRiskTypeDto> children = typeDto.getChildren();
        if (children == null || children.isEmpty()) {
            return;
        }
        for (SafeRiskTypeDto child : children) {
            editDto(save, child);
        }
    }

    @Override
    public Map<String, Object> getShuffleRules(List<SafeRiskTypeDto> selects) {
        Map<String, Object> result = Maps.newHashMap();
        if (selects == null || selects.isEmpty()) {
            return result;
        }
        String level = "LEVEL";
        for (SafeRiskTypeDto select : selects) {
            List<SafeRiskType> pramList = Lists.newArrayList();
            final String id = select.getId();
            final Optional<SafeRiskType> byId = safeRiskTypeRepository.findById(id);
            if (!byId.isPresent()) {
                throw new BadRequestException("场景风险类别[" + select.getName() + "]不存在");
            }
            queryParent(pramList, byId.get());
            // 第一个是叶子节点，最后一个是根节点，反转list
            Collections.reverse(pramList);
            Map<String, Object> param = Maps.newHashMap();
            for (int i = 0; i < pramList.size(); i++) {
                param.put(level + (i + 1), pramList.get(i).getLevel());
            }
            result.put(id, RuleFirer.fire(shuffleProperties.getRuleSetId(), shuffleProperties.getRuleId(), param));
        }
        return result;
    }

    private void queryParent(List<SafeRiskType> pramList, SafeRiskType safeRiskType) {
        if (pramList == null) {
            return;
        }
        pramList.add(safeRiskType);
        final String parentId = safeRiskType.getParentId();
        if (StringUtils.isBlank(parentId)) {
            return;
        }
        final Optional<SafeRiskType> byId = safeRiskTypeRepository.findById(parentId);
        if (!byId.isPresent()) {
            return;
        }
        queryParent(pramList, byId.get());
    }

    @Override
    public List<String> queryTags() {
        List<SafeRiskType> leafTypeList = findLeafSafeRiskTypeList();
        if (leafTypeList.isEmpty()) {
            return Lists.newArrayList();
        }
        return leafTypeList.stream().map(SafeRiskType::getName).distinct().collect(Collectors.toList());
    }

    private List<SafeRiskType> findLeafSafeRiskTypeList() {
        List<SafeRiskType> leafTypeList = Lists.newArrayList();
        final List<SafeRiskType> parentList = safeRiskTypeRepository.findAllByParentIdIsNull();
        findAllLeafType(leafTypeList, parentList, getChildrenTypeMap());
        return leafTypeList;
    }

    /**
     * 查找所有叶子节点Type
     *
     * @param leafTypeList 所有叶子节点Type列表
     * @param parentList   父节点Type列表
     * @param childrenMap  所有子节点Type映射
     */
    private void findAllLeafType(List<SafeRiskType> leafTypeList, List<SafeRiskType> parentList, Map<String, List<SafeRiskType>> childrenMap) {
        if (parentList == null || parentList.isEmpty()) {
            return;
        }
        for (SafeRiskType parent : parentList) {
            final List<SafeRiskType> childrenList = childrenMap.get(parent.getId());
            if (childrenList == null) {
                leafTypeList.add(parent);
                continue;
            }
            findAllLeafType(leafTypeList, childrenList, childrenMap);
        }
    }

    @Override
    public Map<String, Object> recommendItem(RecommendParam param, Pageable pageable) {
        final Integer recommendType = param.getRecommendType();
        final String requirementIdList = param.getRequirementId();
        List<SafeRiskType> leafTypeList = findLeafSafeRiskTypeList();
        Map<String, Object> result;
        switch (recommendType) {
            case 0:
                if (StringUtils.isEmpty(requirementIdList)) {
                    throw new BadRequestException("智能推荐1时，需求id列表不能为空");
                }
                List<String> id1 = new ArrayList<>();
                String[] did1 = requirementIdList.split(",");
                for (String did : did1) {
                    id1.add(did);
                }
                result = matchRequirement(requirementRepository.findAllById(id1), leafTypeList, pageable);
                break;
            case 1:
                final String inputText = param.getInputText();
                if (StringUtils.isBlank(inputText)) {
                    throw new BadRequestException("智能推荐2时，输入文本不能为空");
                }
                result = matchText(inputText, leafTypeList, pageable);
                break;
            case 2:
                if (StringUtils.isEmpty(requirementIdList)) {
                    throw new BadRequestException("智能推荐3时，需求id列表不能为空");
                }
                List<String> id2 = new ArrayList<>();
                String[] did2 = requirementIdList.split(",");
                for (String did : did2) {
                    id2.add(did);
                }
                result = matchSystem(requirementRepository.findAllById(id2), leafTypeList, pageable);
                break;
            default:
                throw new BadRequestException("推荐类型[" + recommendType + "]不支持");
        }
        return result;
    }

    /**
     * 智能推荐1
     * 根据需求主题、描述解析关键字匹配
     *
     * @param requirementList 需求列表
     * @param leafTypeList    所有安全类型叶子节点
     * @param pageable        案例列表分页参数
     * @return 案例规则数据
     */
    private Map<String, Object> matchRequirement(List<Requirement> requirementList, List<SafeRiskType> leafTypeList, Pageable pageable) {
        if (requirementList.isEmpty() || leafTypeList.isEmpty()) {
            return Maps.newHashMap();
        }
        // 根据需求主题、描述解析出List<目录名>
        List<SafeRiskType> targetTypeList = Lists.newArrayList();
        for (Requirement requirement : requirementList) {
            final String str = StringUtils.getStringOrEmpty(requirement.getRequirementTheme()) +
                    StringUtils.getStringOrEmpty(requirement.getRequirementDesc());
            targetTypeList.addAll(leafTypeList.stream().filter(safeRiskType -> str.contains(safeRiskType.getName())).collect(Collectors.toList()));
        }
        return getPageableSafeRiskItemMap(targetTypeList, pageable);
    }

    /**
     * 智能推荐2
     * 根据录入文字信息解析关键字匹配
     *
     * @param inputText    录入文本信息
     * @param leafTypeList 所有安全类型叶子节点
     * @param pageable     案例列表分页参数
     * @return 案例规则数据
     */
    private Map<String, Object> matchText(String inputText, List<SafeRiskType> leafTypeList, Pageable pageable) {
        // 根据录入的文字信息解析出List<目录名>
        List<SafeRiskType> targetTypeList = Lists.newArrayList();
        targetTypeList.addAll(leafTypeList.stream().filter(safeRiskType -> inputText.contains(safeRiskType.getName())).collect(Collectors.toList()));
        return getPageableSafeRiskItemMap(targetTypeList, pageable);
    }

    /**
     * 智能推荐3
     * 根据系统标签信息解析关键字匹配
     *
     * @param requirementList 需求列表
     * @param leafTypeList    所有安全类型叶子节点
     * @param pageable        案例列表分页参数
     * @return 案例规则数据
     */
    private Map<String, Object> matchSystem(List<Requirement> requirementList, List<SafeRiskType> leafTypeList, Pageable pageable) {
        if (requirementList.isEmpty()) {
            return Maps.newHashMap();
        }
        // 根据录入的文字信息解析出List<目录名>
        List<SafeRiskType> targetTypeList = Lists.newArrayList();
        for (Requirement requirement : requirementList) {
            final System system = requirement.getSystem();
            final String tagValue = system.getTagValue();
            if (StringUtils.isEmpty(tagValue)) {
                continue;
            }
            targetTypeList.addAll(leafTypeList.stream().filter(safeRiskType -> tagValue.contains(safeRiskType.getName())).collect(Collectors.toList()));
        }
        return getPageableSafeRiskItemMap(targetTypeList, pageable);
    }

    /**
     * 返回案例规则数据
     *
     * @param targetTypeList 符合匹配List<目录名>
     * @param pageable       分页信息
     * @return 案例规则数据
     */
    private Map<String, Object> getPageableSafeRiskItemMap(List<SafeRiskType> targetTypeList, Pageable pageable) {
        if (targetTypeList.isEmpty()) {
            return Maps.newHashMap();
        }
        final Set<String> typeIds = targetTypeList.stream().map(SafeRiskType::getId).collect(Collectors.toSet());
//        Page<SafeRiskItem> page = safeRiskItemRepository.findAll((root, criteriaQuery, criteriaBuilder) ->
//                QueryHelp.getPredicate(root, new SafeRiskItemQueryCriteria(typeIds), criteriaBuilder), pageable);
//        return PageUtil.toPage(page.map(safeRiskItemMapper::toDto));

        Map<String,Object> map = new LinkedHashMap<>(2);
        List<SafeRiskItem> result = new ArrayList<>();
        if(!typeIds.isEmpty()){
            Map<String, Object> param = Maps.newHashMap();
            typeIds.forEach(str ->{
                param.put("searchValue",str);
                Map<String, Object> fire = RuleFirer.fire(shuffleProperties.getRuleSetId(), "search", param);
                if(fire.get("SAFE_RULES") instanceof  ArrayList){
                    List<Map<String,Object>> safeRules = (ArrayList) fire.get("SAFE_RULES");
                    safeRules.forEach(rule ->{
                        result.add(SafeTestItemServiceImpl.mapToBeanList(rule));
                    });
                }
            });
        }
        map.put("content",result);
        map.put("totalElements",result.size());
        return map;
    }
}
